/*
 Jeffrey Klarfeld (je542304)
 William Greenburg (wi328885)
 
 Assignment #1
 
 */

#include "vm.h"

/********Environment Struct********/
// Struct designed to hold the environment at
// any given point, rather than using Global Variables
typedef struct environment
{
    int SP;                           // stack pointer
    int BP;                           // base pointer
    int PC;                           // program counter
    instruction IR;                   // instruction register
    int stack[MAX_STACK_HEIGHT];      // the stack
    int toJump;                       // address to jump to. Necessary?
}env;

/********Function Prototypes********/

//Prints the state of the program given the environment.
void printProgramState(FILE* outputFile, env currentEnv, FILE* vmOutput);

//Prints the read in code from input.txt (or whatever text file is read in if we switch to 
//command line arguments)
void printInstruction(instruction instruction, int PC, FILE* output, FILE* vmOutput);

//Returns a variable L lexical levels down the stack
int base(int l, int basep, int* stack);

// The op function prototypes. Each One takes a pointer to our Environment
// and updates it.
void lit(int lex_level, int m, env* currentEnv);
void opr(int lex_level, int m, env* currentEnv);
void lod(int lex_level, int m, env* currentEnv);
void sto(int lex_level, int m, env* currentEnv);
void cal(int lex_level, int m, env* currentEnv);
void inc(int lex_level, int m, env* currentEnv);
void jmp(int lex_level, int m, env* currentEnv);
void jpc(int lex_level, int m, env* currentEnv);
void sio(int lex_level, int m, env* currentEnv, FILE* output);


/********MAIN********/
int runVM(FILE* outPtr, FILE* vmInputPtr, instruction* code, int codeLength)
{
    
    instruction nullInstruction = {0,0,0};
    //Initialize the vm's environment
    env currentEnv;
    currentEnv.SP = 0;
    currentEnv.BP = 1;
    currentEnv.PC = 0;
    currentEnv.IR = nullInstruction;
    
    int i = 0;
    

    // Initializing Stack data to Zero
    // for safety
    for(i=0; i<MAX_STACK_HEIGHT; i++)
    {
      currentEnv.stack[i] = 0; 
    }
    currentEnv.stack[1] = 0;
    currentEnv.stack[2] = 0;
    currentEnv.stack[3] = 0;
    
    //Code storage: retains static code data. 
    //              Fetch cycle: env.IR = code[env.PC]
    //Size may not matter unless he dictated this in class, but I can't remember
    // W: I think that "CODE_LENGTH" is the size of the code store
        
    FILE *output = outPtr;
    FILE *vmOutput = vmInputPtr;

/* HW3: Don't need to read code from file anymore, but retain this
 * for now so we can use it for reference in the parser code
    //input read from Command Line arguments
    // First string after ./[compile_name] is the input
    // Second string is the output
    // Example:
    // gcc -o main main.c
    // ./main input.txt output.txt
    // This will read input from input.txt and output to output.txt
    input = fopen(argv[1], "r");
    output = fopen(argv[2], "w");

    //Handling for "File Not Found"
    if (argv[1] == NULL)
    {
        printf("File could not be found\n");
        return 0;
    }

    int codeLength = 0;

    //Read input until EOF or until we've exceeded the code length
    while(!feof(input) && codeLength <= MAX_CODE_LENGTH)
    {
        fscanf(input, "%d %d %d", &code[codeLength].op, &code[codeLength].l, &code[codeLength].m);
        codeLength++;
    }
*/

    //Printing Input as given in spec
    // NOTE: Also produces a second output file designed to be
    //       run directly in the standalone HW1 Virtual Machine
    fprintf(output, "Line   OP    L    M\n");
    if(vmOutput != 0) fprintf(vmOutput, "Line   OP    L    M\n");
    for(i=0;i<codeLength-1;i++)
    {
        printInstruction(code[i], i, output, vmOutput);
        fprintf(output, "\n");
        if(vmOutput != 0) fprintf(vmOutput, "\n");
    }

    //adding a couple lines after the input
    fprintf(output, "\n\n");

    // Printing initial state of the machine
    fprintf(output, "Line   OP    L    M   pc   bp   sp   stack\n");
    fprintf(output, "Initial values      %4d %4d %4d\n", currentEnv.PC, currentEnv.BP, currentEnv.SP);
    
    // While the base pointer > 0, the program hasn't returned, so continue
    for(i=0; currentEnv.BP > 0 && currentEnv.PC < codeLength; i++)
    {
        //BEGIN i'TH FETCH CYCLE
        currentEnv.IR = code[currentEnv.PC];
        printInstruction(currentEnv.IR, currentEnv.PC, output, 0);
        currentEnv.PC++;
        
        //BEGIN i'TH EXECUTE CYCLE
        switch(currentEnv.IR.op)
        {
            case 1:
                lit(0, currentEnv.IR.m, &currentEnv);
                break;
            case 2:
                opr(0, currentEnv.IR.m, &currentEnv);
                break;
            case 3:
                lod(currentEnv.IR.l, currentEnv.IR.m, &currentEnv);
                break;
            case 4:
                sto(currentEnv.IR.l, currentEnv.IR.m, &currentEnv);
                break;
            case 5:
                cal(currentEnv.IR.l, currentEnv.IR.m, &currentEnv);
                break;
            case 6:
                inc(0, currentEnv.IR.m, &currentEnv);
                break;
            case 7:
                jmp(0, currentEnv.IR.m, &currentEnv);
                break;
            case 8:
                jpc(0, currentEnv.IR.m, &currentEnv);
                break;
            case 9:
                sio(0, 1, &currentEnv, output);
                break;
            case 10:
                sio(0, 2, &currentEnv, output);
                break;
            default: 
                fprintf(output, "ERROR\n");
                break;
        }
        
        printProgramState(output, currentEnv, vmOutput);
    }

    return 0;
}

int memberOf(int x, int* arr, int size){
  int i;
  for(i=0; i<size; i++){
    if(x == arr[i]) return 1;
  }
  return 0;
}

void printProgramState(FILE* ofp, env c, FILE* vmOutput)
{
  //Line# OP L M  pc bp sp 
  fprintf(ofp, " %4d %4d %4d   ", c.PC, c.BP, c.SP);

  //printf("%d %d\n", c.BP, c.IR.l);
  
  //print stack
  int i, stackHeight;
  if(c.SP == 0) 
  {
    fprintf(ofp, "0");
    //fprintf(vmOutput, "0");
  }
  else
  {
    if(c.SP >= c.BP+3) stackHeight = c.SP;
    else stackHeight = c.BP+3;
    int bps[3];
    bps[0] = c.BP;
    bps[1] = c.stack[bps[0] + 2];
    bps[2] = c.stack[bps[1] + 2];
    for(i=1; i <= stackHeight; i++)
    {
      fprintf(ofp, "%d ", c.stack[i]);
      //fprintf(vmOutput, "%d ", c.stack[i]);
      if(c.BP != 1 && i != stackHeight && memberOf(i+1, bps, 3) == 1)
        fprintf(ofp, "| ");
        //fprintf(vmOutput, "| ");
    }
  }
  fprintf(ofp, "\n");
  //fprintf(vmOutput, "\n");
  
}

void printInstruction(instruction instruction, int PC, FILE* output, FILE* vmOutput)
{
    int i=PC; 
    fprintf(output, "%4d ", i);
    if(vmOutput != 0) fprintf(vmOutput, "%4d ", i);     
    switch(instruction.op)
    {
        case 1:
            fprintf(output, " lit");
            if(vmOutput != 0) fprintf(vmOutput, " lit");
            break;
        case 2:
            fprintf(output, " opr");
            if(vmOutput != 0) fprintf(vmOutput, " opr");
            break;
        case 3:
            fprintf(output, " lod");
            if(vmOutput != 0) fprintf(vmOutput, " lod");
            break;
        case 4:
            fprintf(output, " sto");
            if(vmOutput != 0) fprintf(vmOutput, " sto");
            break;
        case 5:
            fprintf(output, " cal");
            if(vmOutput != 0) fprintf(vmOutput, " cal");
            break;
        case 6:
            fprintf(output, " inc");
            if(vmOutput != 0) fprintf(vmOutput, " inc");
            break;
        case 7:
            fprintf(output, " jmp");
            if(vmOutput != 0) fprintf(vmOutput, " jmp");
            break;
        case 8:
            fprintf(output, " jpc");
            if(vmOutput != 0) fprintf(vmOutput, " jpc");
            break;
        case 9:
            fprintf(output, " sio");
            if(vmOutput != 0) fprintf(vmOutput, " sio");
            break;
        case 10:
            fprintf(output, " sio");
            if(vmOutput != 0) fprintf(vmOutput, " sio");
            break;
        default: 
            fprintf(output, " ERR: %d", instruction.op);
            if(vmOutput != 0) fprintf(vmOutput, " ERR: %d", instruction.op);
            break;
    }
    fprintf(output, " %4d %4d", instruction.l, instruction.m);
    if(vmOutput != 0) fprintf(vmOutput, " %4d %4d", instruction.l, instruction.m);   
}

void lit(int lex_level, int m, env* currentEnv){
  currentEnv->SP++;
  currentEnv->stack[currentEnv->SP] = m;
}

void opr(int lex_level, int m, env* c)
{
  //Actually executes the operations
  switch(m)
  {
    case 0: //return
      c->SP = c->BP - 1;
      c->PC = c->stack[c->SP + 4];
      c->BP = c->stack[c->SP + 3];
      break;
    case 1: //negate
      c->stack[c->SP] *= -1;
      break;
    case 2: //add
      c->SP--;
      c->stack[c->SP] += c->stack[c->SP + 1];
      break;
    case 3: //subtract
      c->SP--;
      c->stack[c->SP] -= c->stack[c->SP + 1];
      break;
    case 4: //multiply
      c->SP--;
      c->stack[c->SP] *= c->stack[c->SP + 1];
      break;
    case 5: //divide
      c->SP--;
      c->stack[c->SP] /= c->stack[c->SP + 1];
      break;
    case 6: //isOdd?
      c->SP--;
      c->stack[c->SP] = (c->stack[c->SP] % 2 == 1) ? 1 : 0;
      break;
    case 7: //modulus
      c->SP--;
      c->stack[c->SP] = (c->stack[c->SP]) % (c->stack[c->SP + 1]);
      break;
    case 8: //==
      c->SP--;
      c->stack[c->SP] = (c->stack[c->SP] == c->stack[c->SP + 1]) ? 1 : 0;
      break;
    case 9: //!=
      c->SP--;
      c->stack[c->SP] = (c->stack[c->SP] != c->stack[c->SP + 1]) ? 1 : 0;
      break;
    case 10: //<
      c->SP--;
      c->stack[c->SP] = (c->stack[c->SP] < c->stack[c->SP + 1]) ? 1 : 0;
      break;
    case 11: //<=
      c->SP--;
      c->stack[c->SP] = (c->stack[c->SP] <= c->stack[c->SP + 1]) ? 1 : 0;
      break;
    case 12: //>
      c->SP--;
      c->stack[c->SP] = (c->stack[c->SP] > c->stack[c->SP + 1]) ? 1 : 0;
      break;
    case 13: //>=
      c->SP--;
      c->stack[c->SP] = (c->stack[c->SP] >= c->stack[c->SP + 1]) ? 1 : 0;
      break;
  }
}

void lod(int lex_level, int m, env* currentEnv)
{
  currentEnv->SP++;
  currentEnv->stack[currentEnv->SP] = currentEnv->stack[base(lex_level, currentEnv->BP, currentEnv->stack) + m];
}

void sto(int lex_level, int m, env* currentEnv)
{
  currentEnv->stack[base(lex_level, currentEnv->BP, currentEnv->stack) + m] = currentEnv->stack[currentEnv->SP];
  currentEnv->SP--;
}

void cal(int lex_level, int m, env* currentEnv){
  int sp = currentEnv->SP;
  currentEnv->stack[sp+1] = 0;
  currentEnv->stack[sp+2] = base(lex_level, currentEnv->BP, currentEnv->stack);
  currentEnv->stack[sp+3] = currentEnv->BP;
  currentEnv->stack[sp+4] = currentEnv->PC;
  currentEnv->BP = sp+1;
  currentEnv->PC = m;
}

void inc(int lex_level, int m, env* currentEnv)
{
  currentEnv->SP += m;
}

void jmp(int lex_level, int m, env* currentEnv)
{
  currentEnv->PC = m;
}

void jpc(int lex_level, int m, env* currentEnv)
{
  if(currentEnv->stack[currentEnv->SP] == 0)
    currentEnv->PC = m;
  currentEnv->SP--;
}

void sio(int lex_level, int m, env* currentEnv, FILE* output)
{
  if(m == 1)
  {
    //fprintf(output, "%d", currentEnv->stack[currentEnv->SP]);
    printf("%d\n", currentEnv->stack[currentEnv->SP]);
    currentEnv->SP--;
  }
  else if(m == 2)
  {
    currentEnv->SP++;
    int input;
    printf("ENTER INTEGER VALUE:");
    scanf("%d", &input);
    printf("\n");
    currentEnv->stack[currentEnv->SP] = input;
  }
}

int base(int l, int basep, int* stack)
{
  int bl;
  bl = basep;
  while(l>0){
    bl=stack[bl+1];
    l--;
  }
  return bl;
}
